//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//system header files
#include <ddk.h>

#include "ddkit.h"
#include "usbapi.h"
#include "rbuf.h"
#include "usbser.h"
#include <Drvpub.h>

#define UDCD_WAIT_EVENT     0xA000D001
#define UDCD_SET_EVENT      0xA000D002

#define UVCP_SIGNAL_OUT     0x08001FFF
#define UVCP_RX_CHAR        0x80001000
#define UVCP_ENQ_FAIL       0x80001002

extern CUsbApi g_UsbApi;

usb_cdc_line_coding g_LineCode = { GS_DEFAULT_DTE_RATE,
                                   GS_DEFAULT_CHAR_FORMAT,
                                   GS_DEFAULT_PARITY,
                                   GS_DEFAULT_DATA_BITS };


Boolean HandleCdcClassReQ(UInt8 Arg, UInt8 **ppbData, UInt16 *pwLen, UInt16 *pwTotLen)
{
    Boolean ret = TRUE;
    switch (Arg) {
        case USB_CDC_REQ_SET_LINE_CODING:
            //m_XmitLength = MIN(LINE_CODE_LEN, pPKG->wLength);
            //m_pXmitData = (Byte*)(&m_LineCode);
            *ppbData  = (UInt8*)&g_LineCode;
            *pwLen    = 0;
            *pwTotLen = 0;
            ret = FALSE;
        	break;

        case USB_CDC_REQ_GET_LINE_CODING:
            // Send IN Data
    	    *ppbData  = (UInt8*)&g_LineCode;
    	    *pwLen    = (UInt16)MINI(sizeof(usb_cdc_line_coding), *pwLen);
    	    *pwTotLen = sizeof(usb_cdc_line_coding);
        	break;

        case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
            *ppbData  = NULL;
            *pwLen    = 0;
            *pwTotLen = 0;
            ret = FALSE;
        	break;

        default:
    	    /* Unknown vendor/class request */
    		DPRINTFN( EP0_MSGS_ON,\
        	("Unknown vendor/class request %2.2X\r\n", Arg));
        	*ppbData  = NULL;
            *pwLen    = 0;
            *pwTotLen = 0;
        	break;
    }
    return ret;
}

void CDCACMDescriptorGen(descriptor *Desc)
{
    device_descriptor cdc_acm_device = {  USB_DT_DEVICE_SIZE,
                                          USB_DT_DEVICE, 0x0110,
                                          USB_CLASS_COMM, 0, 0,
                                          EP0Len, 0xA525, 0xa4a7,
                                          0, 0, 0, 0,
                                          GS_NUM_CONFIGS
                                       };
    usb_config cdc_acm_cfg_desc = {
        { USB_DT_CONFIG_SIZE, USB_DT_CONFIG, sizeof(usb_config), 2,
          GS_ACM_CONFIG_ID, 0, USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, 0x01},
        { USB_DT_INTERFACE_SIZE, USB_DT_INTERFACE, GS_CONTROL_INTERFACE_ID,
          0, 1, USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_CTRL_PROTO_AT, 0 },
        { //{5, USB_DT_CS_INTERFACE, USB_CDC_HEADER, 0x0110},
          {5, USB_DT_CS_INTERFACE, USB_CDC_CALL_MGMT, 0, 0},
          {4, USB_DT_CS_INTERFACE, USB_CDC_ACM, 0x00},
          {5, USB_DT_CS_INTERFACE, USB_CDC_UNION, 0, 1},
          {USB_DT_ENDPOINT_SIZE, USB_DT_ENDPOINT, 0x83, USB_ENDPOINT_XFER_INT,
           sizeof(SER_NOTIFY), 0x01} },
        { USB_DT_INTERFACE_SIZE, USB_DT_INTERFACE, GS_DATA_INTERFACE_ID,
          0, 2, USB_CLASS_CDC_DATA, 0, 0, 0 },
        { USB_DT_ENDPOINT_SIZE, USB_DT_ENDPOINT, 0x81, USB_ENDPOINT_XFER_BULK,
    	  EP1Len, 0x01 },
        { USB_DT_ENDPOINT_SIZE, USB_DT_ENDPOINT, 0x02, USB_ENDPOINT_XFER_BULK,
    	  EP2Len, 0x01 },
    };

    Desc->String.pbString = (byte*)"12345678901234567";
    Desc->String.bLength = 18;
    memcpy((void*)&(Desc->Device), (void*)&cdc_acm_device, sizeof(device_descriptor));
    memcpy((void*)&(Desc->Config), (void*)&cdc_acm_cfg_desc, sizeof(usb_config));

}

ECode SER_CrThread(void *arg)
{
    ((class CUsbSer*)arg)->TransferThread();
    return NOERROR;
}

ECode CUsbSer::CreateTaskThread()
{
    ECode ec;
    ec = DzCreateKernelThread(SER_CrThread,
        (PVoid)this, CreateThreadFlag_System, ThreadPriorityRank_Normal, &m_pCtrlThd);

    if (FAILED(ec)) {
        DPRINTFN(DBG_ERR, ("unable to create ctrl thread ec : %08x\n", ec));
        return ec;
    }
    return ec;
}

void CUsbSer::ProcessBOTransfer()
{
    UInt32 cbRead, cbEnq;
    cbRead = m_pUfunc->HandleDataOut(m_pbRx, m_RXpipe.Pipelen);
    if (!cbRead) {
        return;
    }
    if (m_IsPostEvent) {
        cbEnq = RBUF_MT_EnQueue(&m_Rbuf, m_pbRx, cbRead);
        if (m_IsSync) {
            m_SyncRd.Pulse();
        }
        if (cbEnq == cbRead) {
 //           m_EventToPost//Rx
            m_EventToPost = UVCP_RX_CHAR;
        }
        else {
            m_EventToPost = UVCP_ENQ_FAIL;
        }
        if (NULL != m_pRbufEvent) {
            DzNotifyEventObjByIsr(m_pRbufEvent, m_EventToPost);
        }
    }
}


ECode CUsbSer::TransferThread()
{
    INTRTYPE intrFlags = INTR_NO_INTR;
    WaitResult wr;
    while (1) {
        m_DevEvent.Wait(&wr, NULL);
        intrFlags = m_pUfunc->GetIntrType();
        DPRINTFN(DBG_ISR, ("Interrupt type %08x \n", intrFlags));

        if ( (INTR_HOST_RESET & intrFlags) || /* If Reset received... */
             (INTR_CFG_CHANGE & intrFlags) || /* Configuration change received... */
             (INTR_CTRL_XFER & intrFlags) ) { /* Setup command received  */
            m_pUfunc->HandleCtrlXfer(intrFlags);
        }

        /* If Bulk Out (receive) interrupt ... */
        if (INTR_RECV_XFER & intrFlags) {
            ProcessBOTransfer();
        }

        m_pUfunc->InterruptDone();
    }

    return NOERROR;
}



int CUsbSer::Initialize()
{
    ECode ec = NOERROR;
    m_IsSync = FALSE;
    m_pUfunc = &g_UsbApi;

    if (NULL == m_pCdcDesc) {
        m_pCdcDesc = (descriptor*)malloc(sizeof(UInt8) * \
                           ( sizeof(string_descriptor) + \
                             USB_DT_DEVICE_SIZE + sizeof(usb_config) ) );
        if (m_pCdcDesc == NULL) {
            ec = E_OUT_OF_MEMORY;
            DPRINTFN(DBG_ERR, ("Generator Descriptor Failed!\n"));
            goto __Exit;
        }
        CDCACMDescriptorGen(m_pCdcDesc);

        if ( FALSE == RBUF_MT_Init(&m_Rbuf, RECV_BUFFER_LEN, sizeof(UInt8))) {
            ec = E_OUT_OF_MEMORY;
            DPRINTFN(DBG_ERR, ("Init Data Buffer Failed!\n"));
            goto __Exit;
        }
    }

    m_pUfunc->SetupDevice(USB_CLASS_COMM, \
                m_pCdcDesc, &HandleCdcClassReQ, &m_DevEvent);
    m_pUfunc->ConfigPipe();
    m_pUfunc->GetPipeInfo(NULL, &m_TXpipe, &m_RXpipe);

    if (NULL == m_pbRx) {
        m_pbRx = (UInt8*) malloc(sizeof(UInt8) * m_RXpipe.Pipelen);
        if ( NULL == m_pbRx) {
            ec = E_OUT_OF_MEMORY;
            DPRINTFN(DBG_ERR, ("Init Receive Fifo Buffer Failed!\n"));
            goto __Exit;
        }
    }

    if (NULL == m_pCtrlThd) {
        ec = CreateTaskThread();
        if (FAILED(ec)) {
            DPRINTFN(DBG_ERR, ("Create Transfer Thread Failed!\n"));
            goto __Exit;
        }
        DPRINTFN(DBG_INIT, ("Create Transfer Thread Succeeded!\n"));
    }
    m_IsInit = TRUE;
    m_pUfunc->DeviceStart();
__Exit:
    return ec;
}

void CUsbSer::DeInit()
{
    m_IsSync = FALSE;
    m_pUfunc = NULL;

    if (m_pCdcDesc) {
        free(m_pCdcDesc);
        m_pCdcDesc = NULL;
    }

    RBUF_MT_DeInit(&m_Rbuf);

    if (m_pbRx) {
        free(m_pbRx);
        m_pbRx = NULL;
    }

    if (m_pCtrlThd) {
        m_pCtrlThd->Release();
        m_pCtrlThd = NULL;
    }
    m_IsInit = FALSE;
}


CUsbSer::CUsbSer()
{
    m_pUfunc = NULL;
    m_pCtrlThd = NULL;
    m_pCdcDesc = NULL;
    m_pbRx = NULL;
    m_EventToPost = 0;
    m_IsPostEvent = TRUE;
    m_pRbufEvent = NULL;
}

CUsbSer::~CUsbSer()
{
    DeInit();
}


void CUsbSer::SER_Stop()
{
    if (m_pUfunc) {
        m_pUfunc->SoftDisconnect();
    }
}

ECode CUsbSer::SER_Wake()
{
    if (FALSE == m_IsInit) {
        return Initialize();
    }
    m_pUfunc->SetupDevice(USB_CLASS_COMM, m_pCdcDesc,\
                     &HandleCdcClassReQ, &m_DevEvent);
    m_pUfunc->ReConnect();
    return NOERROR;
}


ECode CUsbSer::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    Int32 OpCode = (Int32)nControlCode;
    ECode ec = NOERROR;

    switch(OpCode) {
        case UCDC_INIT://Protocol Initialize, Do not Expose to User, use for self test...
/*Temporarily Shut off
            ec = Initialize();
*/
            break;

        case UCDC_STOP:
            SER_Stop();
            break;

        case UCDC_CLR_BUF:
            //To Do
            //Clear Buffer
            RBUF_MT_Reset(&m_Rbuf);
            break;

        case UCDC_WAKE:
            ec = SER_Wake();
            if (FAILED(ec)) {
                DeInit();
            }
            else {
                cputs("Emulate High Speed Serial Port ... !\n");
            }
            break;

        case UCDC_SET_SYNC:
            m_IsSync = TRUE;
            break;

        case UCDC_SET_ASYNC:
            m_IsSync = FALSE;
            break;

        case UCDC_SET_PRIF:
            //Time Out And else
            break;

        case UDCD_WAIT_EVENT:
            if (FALSE == m_IsPostEvent) {
                m_IsPostEvent = TRUE;
            }
            if (NULL != m_pRbufEvent) {
                m_pRbufEvent->Release();
                m_pRbufEvent = NULL;
            }
            if (NULL == ppCompletionEvent) {
                ec = E_INVALID_ARGUMENT;
                break;
            }
            DzCreateEventObj(FALSE, UNSIGNALED, &m_pRbufEvent);
            m_pRbufEvent->AddRef();
            *ppCompletionEvent = m_pRbufEvent;
            break;

        case UDCD_SET_EVENT:
            if (NULL != m_pRbufEvent) {
                m_IsPostEvent = FALSE;
                RBUF_MT_Reset(&m_Rbuf);
                m_pRbufEvent->Notify(UVCP_SIGNAL_OUT);
                m_pRbufEvent->Release();
                m_pRbufEvent = NULL;
            }
            break;

        default:
            DPRINTFN(1, ("USB CDC DEVICE UnKnown OpCode %08x\n", OpCode));
            ec = E_INVALID_ARGUMENT;
    }
    return ec;
}


ECode CUsbSer::Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    UInt32 Cnt, cbRead;
    UInt8 *pdata;
    WaitResult wr;

    m_Lock.Lock(&wr);
    if (pBuffer == NULL) {
        return E_INVALID_ARGUMENT;
    }
    pdata = CONV(UInt8*, pBuffer->GetPayload());
    cbRead = 0;
    Cnt = bytesToRead;
    if (m_IsSync) {
        while(Cnt) {
            cbRead = RBUF_MT_DeQueue(&m_Rbuf, pdata, Cnt);
            Cnt -= cbRead;
            pBuffer += cbRead;
            if (0 == cbRead) {
                m_SyncRd.Wait(&m_Lock, NULL);
            }
        }
        Cnt = bytesToRead;
    }
    else {
        Cnt = RBUF_MT_DeQueue(&m_Rbuf, pdata, Cnt);
   }

    SETBUF(UInt8, Cnt, pBuffer);
    m_Lock.Unlock();

    return NOERROR;
}


ECode CUsbSer::Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    UInt32 cbWrite, Cnt;
    UInt8 *pdata;
    WaitResult wr;

    m_Lock.Lock(&wr);
    Cnt = buffer.GetUsed();
    pdata = CONV(UInt8*, buffer.GetPayload());

    TIMEOUT_INIT(PACKET_TIMEOUT);

    while (Cnt) {
        if (EP1DBLen == Cnt || EP1Len == Cnt) {//Hardware bugifx
            cbWrite = m_pUfunc->HandleDataIn(pdata, Cnt -1, 1);
            if (cbWrite) TIMEOUT_RESET();

            Cnt -= cbWrite;
            pdata += cbWrite;

            if (cbWrite) {
                cbWrite = m_pUfunc->HandleDataIn(pdata, 1, 1);
                if (cbWrite) TIMEOUT_RESET();

                Cnt -= cbWrite;
                pdata += cbWrite;
            }
        }
        else {
            cbWrite = (Cnt > m_TXpipe.Pipelen) ? m_TXpipe.Pipelen : Cnt;
            cbWrite = m_pUfunc->HandleDataIn(pdata, cbWrite, 1);
            if (cbWrite) TIMEOUT_RESET();

            Cnt -= cbWrite;
            pdata += cbWrite;
        }

        if (TIMEOUT_FOUNDED()) {
            kprintf("UsbSer write timeout!\n");
            m_Lock.Unlock();
            return E_TIMED_OUT;
        }
    }
    *pBytesWritten = buffer.GetUsed() - Cnt;
    m_Lock.Unlock();
    return NOERROR;
}

void CUsbSer::Dispose()
{
}

CUsbSer g_UsbSer;
EXTERN IDeviceDriver * CDECL CreateUsbSer(uint_t uDeviceNo, void *pvParameter)
{
    cputs("Register USB Virtual Serial!\n");
    g_UsbSer.m_IsInit = FALSE;
    g_UsbSer.m_pUfunc = &g_UsbApi;
    return (&g_UsbSer);
}



